package com.dspot.declex.holder;

import static com.helger.jcodemodel.JExpr._new;
import static com.helger.jcodemodel.JExpr._null;
import static com.helger.jcodemodel.JExpr.cast;
import static com.helger.jcodemodel.JExpr.invoke;
import static com.helger.jcodemodel.JExpr.ref;

import java.util.HashMap;
import java.util.Map;

import javax.lang.model.element.Element;

import org.ohosannotations.helper.ADIHelper;
import org.ohosannotations.holder.EComponentHolder;
import org.ohosannotations.holder.EComponentWithViewSupportHolder;
import org.ohosannotations.internal.process.ProcessHolder;
import org.ohosannotations.plugin.PluginClassHolder;

import com.dspot.declex.annotation.ExportPopulate;
import com.dspot.declex.annotation.Model;
import com.dspot.declex.annotation.Populate;
import com.dspot.declex.api.action.runnable.OnFailedRunnable;
import com.dspot.declex.util.SharedRecords;
import com.dspot.declex.util.TypeUtils;
import com.dspot.declex.util.TypeUtils.ClassInformation;

import org.ohosannotations.internal.virtual.VirtualElement;

import com.helger.jcodemodel.IJStatement;
import com.helger.jcodemodel.JBlock;
import com.helger.jcodemodel.JCatchBlock;
import com.helger.jcodemodel.JConditional;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JFieldRef;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JMod;
import com.helger.jcodemodel.JTryBlock;
import com.helger.jcodemodel.JVar;

/**
 * 填充夹
 *
 * @author ohos
 * @date 2021/07/27
 */
public class PopulateHolder extends PluginClassHolder<EComponentWithViewSupportHolder> {
    /**
     * 独特的优先计数器
     */
    private static int uniquePriorityCounter = 5000;

    /**
     * 填充方法
     */
    private Map<Element, JMethod> populateMethods = new HashMap<>();
    /**
     * 填充方法阻止
     */
    private Map<Element, JBlock> populateMethodsBlock = new HashMap<>();
    /**
     * 填充这个方法
     */
    private JMethod populateThisMethod;

    /**
     * 填充的听众
     *///PopulateListeners are shared through all the PopulateHolders
    private static Map<Element, JFieldRef> populateListeners = new HashMap<>();

    /**
     * adi助手
     */
    private ADIHelper adiHelper;

    /**
     * 填充夹
     *
     * @param holder 持有人
     */
    public PopulateHolder(EComponentWithViewSupportHolder holder) {
        super(holder);

        this.adiHelper = new ADIHelper(environment());

    }

    /**
     * 得到填充方法
     *
     * @param element 元素
     * @return {@link JMethod}
     */
    public JMethod getPopulateMethod(Element element) {
        JMethod populateMethod = populateMethods.get(element);
        if (populateMethod == null) {
            setPopulateMethod(element);
            populateMethod = populateMethods.get(element);
        }

        return populateMethod;
    }

    /**
     * 得到填充方法块
     *
     * @param element 元素
     * @return {@link JBlock}
     */
    public JBlock getPopulateMethodBlock(Element element) {
        JBlock populateMethodBlock = populateMethodsBlock.get(element);
        if (populateMethodBlock == null) {
            setPopulateMethod(element);
            populateMethodBlock = populateMethodsBlock.get(element);
        }

        return populateMethodBlock;
    }

    /**
     * 填充这个
     *
     * @return {@link JMethod}
     */
    public JMethod getPopulateThis() {
        if (populateThisMethod == null) {
            setPopulateThis();
        }
        return populateThisMethod;
    }

    /**
     * 得到填充侦听器
     *
     * @param element 元素
     * @return {@link JFieldRef}
     */
    public JFieldRef getPopulateListener(Element element) {
        final boolean hasExportPopulate = adiHelper.getAnnotation(element, ExportPopulate.class) != null;
        if (!hasExportPopulate) return null;

        JFieldRef populateListener;
        if (element instanceof VirtualElement) {
            populateListener = populateListeners.get(((VirtualElement) element).getElement());
        } else {
            populateListener = populateListeners.get(element);
        }

        if (populateListener == null) {
            if (element instanceof VirtualElement) {
                setPopulateMethod(element);
                populateListener = populateListeners.get(((VirtualElement) element).getElement());
            } else {
                final String fieldName = element.getSimpleName().toString();
                final String populateListenerName = "populate" + fieldName.substring(0, 1).toUpperCase()
                    + fieldName.substring(1);
                populateListener = ref(populateListenerName);
            }
        }

        return populateListener;
    }

    /**
     * 设置填充方法
     *
     * @param element 元素
     */
    private void setPopulateMethod(Element element) {

        final String fieldName = element.getSimpleName().toString();

        JMethod populateMethod = holder().getGeneratedClass().method(JMod.NONE, getCodeModel().VOID, "_populate_" + fieldName);
        JVar afterPopulate = populateMethod.param(JMod.FINAL, getJClass(Runnable.class), "afterPopulate");
        populateMethod.param(JMod.FINAL, getJClass(OnFailedRunnable.class), "onFailed");

        populateMethods.put(element, populateMethod);

        JMethod populateThisMethod = getPopulateThis();
        if (!adiHelper.getAnnotation(element, Populate.class).independent()) {
            populateThisMethod.body().add(invoke(populateMethod).arg(_null()).arg(ref("onFailed")));
        }

        JTryBlock tryBlock = populateMethod.body()._try();
        {//Catch block
            JCatchBlock catchBlock = tryBlock._catch(getClasses().THROWABLE);
            JVar caughtException = catchBlock.param("e");

            IJStatement uncaughtExceptionCall = getClasses().THREAD
                .staticInvoke("getDefaultUncaughtExceptionHandler")
                .invoke("uncaughtException")
                .arg(getClasses().THREAD.staticInvoke("currentThread"))
                .arg(caughtException);

            JFieldRef onFailed = ref("onFailed");
            JConditional ifOnFailedAssigned = catchBlock.body()._if(onFailed.ne(_null()));
            ifOnFailedAssigned._then().add(invoke(onFailed, "onFailed").arg(caughtException));
            ifOnFailedAssigned._else().add(uncaughtExceptionCall);
        }
        populateMethodsBlock.put(element, tryBlock.body().blockVirtual());

        tryBlock.body()._if(afterPopulate.ne(_null()))._then()
            .invoke(afterPopulate, "run");

        callPopulateAfterModelLoaded(element, populateMethod);
    }

    /**
     * 调用模型加载后填充
     *
     * @param element 元素
     * @param populateMethod 填充方法
     */
    private void callPopulateAfterModelLoaded(Element element, JMethod populateMethod) {

        final String fieldName = element.getSimpleName().toString();
        final boolean hasExportPopulate = adiHelper.getAnnotation(element, ExportPopulate.class) != null;

        Model model = adiHelper.getAnnotation(element, Model.class);
        if (model != null) {
            final ModelHolder modelHolder;

            //Support ExportPopulate
            if (hasExportPopulate) {

                final Element referenceElement = ((VirtualElement) element).getReference();
                ClassInformation classInformation = TypeUtils.getClassInformation(referenceElement, environment(), true);
                ProcessHolder processHolder = environment().getProcessHolder();
                EComponentHolder holder = (EComponentHolder) processHolder.getGeneratedClassHolder(classInformation.generatorElement);

                modelHolder = holder.getPluginHolder(new ModelHolder(holder));
                element = ((VirtualElement) element).getElement();

                if (populateListeners.containsKey(element)) {
                    //This means the call was already created
                    return;
                }
                ;

            } else {
                modelHolder = holder().getPluginHolder(new ModelHolder(holder()));
            }

            JBlock methodBody = modelHolder.getAfterLoadModelBlock(element);

            if (model.async()) {
                JDefinedClass anonymousRunnable = getCodeModel().anonymousClass(Runnable.class);
                JMethod anonymousRunnableRun = anonymousRunnable.method(JMod.PUBLIC, getCodeModel().VOID, "run");
                anonymousRunnableRun.annotate(Override.class);

                JVar handler = methodBody.decl(getClasses().HANDLER, "handler",
                    _new(getClasses().HANDLER).arg(getClasses().LOOPER.staticInvoke("getMainLooper")));
                methodBody.add(invoke(handler, "post").arg(_new(anonymousRunnable)));

                methodBody = anonymousRunnableRun.body();
            }

            JBlock ifPopulate = methodBody._if(
                ref("args").invoke("containsKey").arg("populate").not()
                    .cor(cast(getJClass(Boolean.class), ref("args").invoke("get").arg("populate")))
            )._then();

            if (hasExportPopulate) {
                final String populateListenerName = "populate" + fieldName.substring(0, 1).toUpperCase()
                    + fieldName.substring(1);

                JFieldRef listenerField = ref(populateListenerName);
                ifPopulate._if(listenerField.neNull())._then()
                    .add(invoke(listenerField, "populateModel").arg(_null()).arg(ref("onFailed")));

                populateListeners.put(element, listenerField);

            } else {
                ifPopulate.add(invoke(populateMethod).arg(_null()).arg(ref("onFailed")));
            }

        }
    }

    /**
     * 设置填充这个
     */
    private void setPopulateThis() {
        populateThisMethod = holder().getGeneratedClass().method(JMod.NONE, getCodeModel().VOID, "_populate_this");
        JVar afterPopulate = populateThisMethod.param(JMod.FINAL, getJClass(Runnable.class), "afterPopulate");
        populateThisMethod.param(JMod.FINAL, getJClass(OnFailedRunnable.class), "onFailed");

        JBlock block = new JBlock();
        block._if(afterPopulate.neNull())._then()
            .invoke(afterPopulate, "run");
        SharedRecords.priorityAdd(populateThisMethod.body(), block, uniquePriorityCounter);
    }
}
